#ifndef OBJFILELOADER_H
#define OBJFILELOADER_H
#pragma  once

#include "DXUT.h"
#include "..\\Effects11\\Inc\\d3dx11effect.h"
#include "MeshRender.h"
#include <vector>
#include "Material.h"
#include "..\\Utility\\D3D\\D3DUtilityFunction.h"
#include "D3DX9MeshToD3D11Mesh.h"


template <class CVertType>
class CObjFileLoader : public CMeshRender
{
public:
	CObjFileLoader():CMeshRender()
	{
		ZeroMemory( m_strMediaDir, sizeof( m_strMediaDir ) );
		STRIDE_SIZE = CVertType::STRIDE_SIZE;
		m_iVType = CVertType::VType;
		m_bSupportLocalCoords = true;
		m_bConstructLocalCoords = false;
	}

	virtual ~CObjFileLoader()
	{
		OnD3D11DestroyDevice(NULL);
	}

	virtual HRESULT OnD3D11CreateDevice( ID3D11Device* pd3dDevice, ID3DX11EffectTechnique* pTech, 
		const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext )
	{
		HRESULT hr = S_OK;

		if(m_pVB == NULL || m_pIB == NULL || !m_bModelReady)
		{
			printf("No geometry data loaded yet!\n");
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
			return E_FAIL;
		}

		//Create vertex layout
		if(m_bConstructLocalCoords)
		{
			V_RETURN(CD3DUtilityFunction::CreateVertexLayout(pd3dDevice,VertexNormUVTang::layout,ARRAYSIZE(VertexNormUVTang::layout),pTech,&m_pVertexLayout));
		}
		else
		{
			V_RETURN(CD3DUtilityFunction::CreateVertexLayout(pd3dDevice,CVertType::layout,ARRAYSIZE(CVertType::layout),pTech,&m_pVertexLayout));
		}


		return hr;
	}	

	virtual void OnD3D11DestroyDevice( void* pUserContext )
	{
		CMeshRender::OnD3D11DestroyDevice(pUserContext);
		Destroy();
	}

	virtual void Render( ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, 
		ID3DX11EffectTechnique* pRenderTech, enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, double fTime, 
		float fElapsedTime, void* pUserContext )
	{
		if(iMeshType != RENDERMESHTECH::RENDERSTATICMESH)
		{
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return;
		}

		HRESULT hr;
		//Set model world matrix
		D3DXMATRIX mWorldMatrix = m_mWorldModel * mWorldCamera;
		hr = (pEffect->GetVariableByName("mWorld")->AsMatrix()->SetMatrix((float*)(&mWorldMatrix)));
		//HRESULT hr = S_OK;
		// Set render resources
		pd3dImmediateContext->IASetInputLayout( m_pVertexLayout );
		ID3D11Buffer* pVB[1] = { m_pVB };
		UINT pStride[1] = {STRIDE_SIZE};
		UINT pOffset[1] = { 0 };
		pd3dImmediateContext->IASetVertexBuffers( 0, 1, pVB, pStride, pOffset );
		pd3dImmediateContext->IASetIndexBuffer(m_pIB, DXGI_FORMAT_R32_UINT, 0);
		pd3dImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Render objects here...
		// Apply the technique contained in the effect 
		D3DX11_TECHNIQUE_DESC Desc;
		pRenderTech->GetDesc( &Desc );
		ID3DX11EffectVectorVariable* hAmbient = pEffect->GetVariableByName("vAmbientColor")->AsVector();
		ID3DX11EffectVectorVariable* hDiffuse = pEffect->GetVariableByName("vDiffuseColor")->AsVector();
		ID3DX11EffectVectorVariable* hSpecular = pEffect->GetVariableByName("vSpecularColor")->AsVector();
		ID3DX11EffectScalarVariable* hSpecularPower = pEffect->GetVariableByName("fSpecularPower")->AsScalar();
		ID3DX11EffectShaderResourceVariable* hDiffuseTex = pEffect->GetVariableByName("texDiffuse")->AsShaderResource();
		ID3DX11EffectScalarVariable* hbTex = pEffect->GetVariableByName("bTexture")->AsScalar();

		for( UINT iPass = 0; iPass < Desc.Passes; iPass++ )
		{
			for (unsigned int iSubset = 0; iSubset < m_Subsets.size(); iSubset++)
			{
				//find the corresponding material index for current subset!
				unsigned int iMatID = m_Subsets[iSubset].MatIndex;
				if(hAmbient)
				{	
					D3DXVECTOR4 ambient = D3DXVECTOR4(m_Materials[iMatID]->vAmbient,1.0f);
					hAmbient->SetFloatVector((float*)(&ambient));
				}
				if(hDiffuse)
				{	
					D3DXVECTOR4 diffuse = D3DXVECTOR4(m_Materials[iMatID]->vDiffuse,1.0f);
					hDiffuse->SetFloatVector((float*)(&diffuse));
				}
				if(hSpecular)
				{	
					D3DXVECTOR4 specular= D3DXVECTOR4(m_Materials[iMatID]->vSpecular,1.0f);
					hSpecular->SetFloatVector((float*)(&specular));
				}
				if(hSpecularPower)
					hSpecularPower->SetFloat(m_Materials[iMatID]->fShininess);

				if(hDiffuseTex&&m_Materials[iMatID]->pTexture)
				{
					hDiffuseTex->SetResource(m_Materials[iMatID]->pTexture);
					hbTex->SetBool(TRUE);
				}
				else 
					hbTex->SetBool(FALSE);

				pRenderTech->GetPassByIndex( iPass )->Apply( 0, pd3dImmediateContext );
				pd3dImmediateContext->DrawIndexed( m_Subsets[iSubset].FaceCount*3, m_Subsets[iSubset].FaceStart*3, 0 );			
			}
		}
	}

	//compute local tangent coords
	//http://www.terathon.com/code/tangent.html
	//
	virtual bool constructLocalCoords()
	{
		if(m_Vertices.size() == 0 || m_Indices.size()==0 || !m_bConstructLocalCoords)
		{
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return false;
		}
		printf("Start constructing local frame:");
		m_Vertices_Local.clear();
		for (unsigned int i = 0; i < m_Vertices.size(); i++ )
		{
			VertexNormUVTang vert( m_Vertices[i].Position, m_Vertices[i].Normal, m_Vertices[i].UV, D3DXVECTOR3(0,0,0),D3DXVECTOR3(0,0,0) );
			m_Vertices_Local.push_back(vert);			
		}

		for(unsigned int i = 0 ; i < m_Indices.size(); i+=3)
		{
			if(i%100 == 0) printf("<");
			unsigned int i1 = m_Indices[i];
			unsigned int i2 = m_Indices[i+1];
			unsigned int i3 = m_Indices[i+2];

			D3DXVECTOR3 *v1 = &(m_Vertices_Local[i1].Position);
			D3DXVECTOR3 *v2 = &(m_Vertices_Local[i2].Position);
			D3DXVECTOR3 *v3 = &(m_Vertices_Local[i3].Position);

			D3DXVECTOR2 *w1 = &(m_Vertices_Local[i1].UV);
			D3DXVECTOR2 *w2 = &(m_Vertices_Local[i2].UV);
			D3DXVECTOR2 *w3 = &(m_Vertices_Local[i3].UV);

			float x1 = v2->x - v1->x;
			float x2 = v3->x - v1->x;
			float y1 = v2->y - v1->y;
			float y2 = v3->y - v1->y;
			float z1 = v2->z - v1->z;
			float z2 = v3->z - v1->z;

			float s1 = w2->x - w1->x;
			float s2 = w3->x - w1->x;
			float t1 = w2->y - w1->y;
			float t2 = w3->y - w1->y;

			float r = 1.0f / (s1 * t2 - s2 * t1);
			D3DXVECTOR3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
			D3DXVECTOR3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);

			m_Vertices_Local[i1].Tangent += sdir;
			m_Vertices_Local[i2].Tangent += sdir;
			m_Vertices_Local[i3].Tangent += sdir;

			m_Vertices_Local[i1].BiNormal += tdir;
			m_Vertices_Local[i2].BiNormal += tdir;
			m_Vertices_Local[i3].BiNormal += tdir;				
		}
		printf("\n");
		for (unsigned int i = 0; i < m_Vertices_Local.size(); i++)
		{
			if(i%100 == 0) printf(">");
			D3DXVECTOR3 n = m_Vertices_Local[i].Normal;
			D3DXVECTOR3 t = m_Vertices_Local[i].Tangent;

			// Gram-Schmidt orthogonalize
			D3DXVECTOR3 tangent;
			float val1 = D3DXVec3Dot(&n,&t);
			D3DXVECTOR3 tmp = D3DXVECTOR3(t-n*val1);					
			D3DXVec3Normalize(&tangent, &tmp);

			// Calculate handedness
			D3DXVec3Cross(&tmp,&n,&t);
			float tanw = ( D3DXVec3Dot(&tmp, &(m_Vertices_Local[i].BiNormal)) < 0.0f) ? -1.0f : 1.0f;
			// calculate the binormal
			D3DXVECTOR3 BiNormal;
			//D3DXVec3Cross(&tmp,&tangent,&n);//right hand
			D3DXVec3Cross(&tmp,&n,&tangent);//left hand
			tmp *= tanw;
			D3DXVec3Normalize(&BiNormal,&tmp);

			// copy the data
			m_Vertices_Local[i].Tangent = tangent;
			m_Vertices_Local[i].BiNormal = BiNormal;
		}
		printf("done!\n");

		m_iVType = VertexNormUVTang::VType;
		STRIDE_SIZE = VertexNormUVTang::STRIDE_SIZE;
#ifdef _DEBUG
		char tmp[MAX_PATH];
		memset(tmp,0,MAX_PATH);
		sprintf(tmp,"The local tangent coord has been built, and CVertType = VT_POSNORMUVTANG\n");
		OUTPUTIMPORTANTINFO(tmp);
#endif
	}
	virtual bool LoadModel(const char* strFileName, ID3D11Device* pd3dDevice)
	{
		Destroy();
		std::string sFileName;
		sFileName.assign(strFileName);
		std::wstring wsFileName = UtilityFunction::strtowstr(sFileName);
		if(LoadGeometryFromOBJ(wsFileName.data())!=S_OK)
			return false;
		if(m_bConstructLocalCoords)
		{
			bool bRet = constructLocalCoords();
			if (!bRet)
			{
#ifdef _DEBUG
				OUTPUTERRORINFO;
#endif
					return false;
			}
		}
		//prepare the subset meshes for rendering
		//PrepareSubsetMeshes();
		// Set the current directory based on where the mesh was found
		WCHAR wstrOldDir[MAX_PATH] = {0};
		GetCurrentDirectory( MAX_PATH, wstrOldDir );
		SetCurrentDirectory( m_strMediaDir );

		//create context
		ID3D11DeviceContext* pContext = NULL;
		pd3dDevice->GetImmediateContext(&pContext);
		// Load material textures
		HRESULT hr = S_OK;
		WCHAR str[ MAX_PATH ] = {0};
		for( int iMaterial = 0; iMaterial < m_Materials.size(); iMaterial++ )
		{
			Material* pMaterial = m_Materials[iMaterial];
			if( pMaterial->strTexture[0] )
			{
				// Avoid loading the same texture twice
				bool bFound = false;
				for( int x = 0; x < iMaterial; x++ )
				{
					Material* pCur = m_Materials[x];
					if( 0 == wcscmp( pCur->strTexture, pMaterial->strTexture ) )
					{
						bFound = true;
						pMaterial->pTexture = pCur->pTexture;
						break;
					}
				}

				// Not found, load the texture
				if( !bFound )
				{
					V( DXUTFindDXSDKMediaFileCch( str, MAX_PATH, pMaterial->strTexture ) );

					SAFE_RELEASE(pMaterial->pTexture);

					std::wstring wsFilename;
					wsFilename.assign(pMaterial->strTexture);
					std::string sFilename = UtilityFunction::wstrtostr(wsFilename);
					if( FAILED(DXUTGetGlobalResourceCache().CreateTextureFromFile( pd3dDevice, pContext, sFilename.data(), &(pMaterial->pTexture)) ) )
						return false;
				}
			}
		}
		//release context
		SAFE_RELEASE(pContext);
		// Restore the original current directory
		SetCurrentDirectory( wstrOldDir );

		// Create the encapsulated mesh
		//create temp d3d9 device
		//create d3d9 device
		LPDIRECT3DDEVICE9 pDev9 = NULL;
		CD3DUtilityFunction::CreateD3D9Device(&pDev9);

		ID3DXMesh* pMesh = NULL;
		if(m_bConstructLocalCoords)
		{
			V( D3DXCreateMesh( m_Indices.size() / 3, m_Vertices_Local.size(),
				D3DXMESH_MANAGED | D3DXMESH_32BIT, VertexNormUVTang::VERTEX_DECL,
				pDev9, &pMesh ) );
		}
		else
		{
			V( D3DXCreateMesh( m_Indices.size() / 3, m_Vertices.size(),
				D3DXMESH_MANAGED | D3DXMESH_32BIT, CVertType::VERTEX_DECL,
				pDev9, &pMesh ) );
		}

		if(pMesh->GetNumBytesPerVertex() != STRIDE_SIZE)
		{
			printf("STRIDE_SIZE is incorrect!\n");
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
			SAFE_RELEASE(pMesh);
			SAFE_RELEASE(pDev9);
			return false;
		}
		// Copy the vertex data
		CVertType* pVertex;
		V( pMesh->LockVertexBuffer( 0, ( void** )&pVertex ) );
		if(m_bConstructLocalCoords)
			memcpy( pVertex, m_Vertices_Local.data(), m_Vertices_Local.size() * sizeof( VertexNormUVTang ) );
		else
			memcpy( pVertex, m_Vertices.data(), m_Vertices.size() * sizeof( CVertType ) );
		pMesh->UnlockVertexBuffer();
		m_Vertices.clear();
		//m_Vertices_Local.clear();

		// Copy the index data
		DWORD* pIndex;
		V( pMesh->LockIndexBuffer( 0, ( void** )&pIndex ) );
		memcpy( pIndex, m_Indices.data(), m_Indices.size() * sizeof( DWORD ) );
		pMesh->UnlockIndexBuffer();
		m_Indices.clear();

		// Copy the attribute data
		DWORD* pSubset;
		V( pMesh->LockAttributeBuffer( 0, &pSubset ) );
		memcpy( pSubset, m_Attributes.data(), m_Attributes.size() * sizeof( DWORD ) );
		pMesh->UnlockAttributeBuffer();
		m_Attributes.clear();

		// Reorder the vertices according to subset and optimize the mesh for this graphics 
		// card's vertex cache. When rendering the mesh's triangle list the vertices will 
		// cache hit more often so it won't have to re-execute the vertex shader.
		DWORD* aAdjacency = new DWORD[pMesh->GetNumFaces() * 3];
		if( aAdjacency == NULL )
			return E_OUTOFMEMORY;

		V( pMesh->GenerateAdjacency( 1e-6f, aAdjacency ) );
		V( pMesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, aAdjacency, NULL, NULL, NULL ) );

		SAFE_DELETE_ARRAY( aAdjacency );
		//Create D3D11 mesh based on d3dx9 mesh
		SAFE_RELEASE(m_pVB);
		SAFE_RELEASE(m_pIB);
		m_Subsets.clear();
		XMeshConv::CreateD3D11MeshFromD3DX9Mesh(pMesh,pd3dDevice,&m_pVB,&m_pIB, m_Subsets);

		//release d3d resources
		SAFE_RELEASE(pMesh);
		SAFE_RELEASE(pDev9);

		m_bModelReady = true;	
		return m_bModelReady;
	}

	void PrepareSubsetMeshes()
	{
		m_Subsets.clear();
		//use default material
		if (m_Materials.size() == 1)
		{
			MESH_ATTRIBUTE_RANGE subset;
			subset.AttribId = 0;
			subset.FaceCount = m_Indices.size()/3;
			subset.FaceStart = 0;
			m_Subsets.push_back(subset);			
		}
		else if(m_Materials.size() > 1)
		{
			//reorder the indices array
			std::vector<MAT_FACE_ID> MatFacesArr;
			for (int i = 0; i < m_Attributes.size(); i++)
			{
				MAT_FACE_ID matfaceid;
				matfaceid.MatID = m_Attributes[i];
				matfaceid.VertID0 = m_Indices[3*i];
				matfaceid.VertID1 = m_Indices[3*i+1];
				matfaceid.VertID2 = m_Indices[3*i+2];
				MatFacesArr.push_back(matfaceid);
			}			
			std::sort(MatFacesArr.begin(),MatFacesArr.end(),MAT_FACE_ID::compare);
			//rewrite the indices array
			int iFaceStart = 0, iFaceCount = 0, iVertStart = 0, iVertCount = 0;
			int iMatID = MatFacesArr[0].MatID;

			for (int i = 0; i < MatFacesArr.size(); i++ )
			{
				if(iMatID != MatFacesArr[i].MatID)
				{
					if(iFaceCount!=0)
						m_Subsets.push_back(MESH_ATTRIBUTE_RANGE(iMatID,iFaceStart,iFaceCount));
					iMatID = MatFacesArr[i].MatID;
					iFaceStart = i;
					iFaceCount = 0;
				}	
				m_Indices[3*i] = MatFacesArr[i].VertID0;
				m_Indices[3*i+1] = MatFacesArr[i].VertID1;
				m_Indices[3*i+2] = MatFacesArr[i].VertID2;
				iFaceCount++;
			}
			//add the last materials
			m_Subsets.push_back(MESH_ATTRIBUTE_RANGE(iMatID,iFaceStart,iFaceCount));
		}
	}

	void Destroy()
	{
		for( int iMaterial = 0; iMaterial < m_Materials.size(); iMaterial++ )
		{
			Material* pMaterial = m_Materials[iMaterial];

			// Avoid releasing the same texture twice
			for( int x = iMaterial + 1; x < m_Materials.size(); x++ )
			{
				Material* pCur = m_Materials[x];
				if( pCur->pTexture == pMaterial->pTexture )
					pCur->pTexture = NULL;
			}

			SAFE_RELEASE( pMaterial->pTexture );
			SAFE_DELETE( pMaterial );
		}

		m_Materials.clear();
		m_Vertices.clear();
		m_Vertices_Local.clear();
		m_Indices.clear();
		m_Attributes.clear();	
		m_Subsets.clear();
	}


	inline UINT  GetNumMaterials() const
	{
		return m_Materials.size();
	}
	inline Material* GetMaterial( UINT iMaterial )
	{
		return m_Materials[iMaterial];
	}

	inline WCHAR* GetMediaDirectory()
	{
		return m_strMediaDir;
	}	
private:

	HRESULT LoadGeometryFromOBJ( const WCHAR* strFileName )
	{
		WCHAR strMaterialFilename[MAX_PATH] = {0};
		WCHAR wstr[MAX_PATH];
		char str[MAX_PATH];
		HRESULT hr;

		// Find the file
		V_RETURN( DXUTFindDXSDKMediaFileCch( wstr, MAX_PATH, strFileName ) );
		WideCharToMultiByte( CP_ACP, 0, wstr, -1, str, MAX_PATH, NULL, NULL );

		// Store the directory where the mesh was found
		wcscpy_s( m_strMediaDir, MAX_PATH - 1, wstr );
		WCHAR* pch = wcsrchr( m_strMediaDir, L'\\' );
		if( pch )
			*pch = NULL;

		// Create temporary storage for the input data. Once the data has been loaded into
		// a reasonable format we can create a D3DXMesh object and load it with the mesh data.
		std::vector <D3DXVECTOR3> Positions;
		std::vector <D3DXVECTOR2> TexCoords;
		std::vector <D3DXVECTOR3> Normals;

		// The first subset uses the default material
		DWORD dwCurSubset = 0;

		// File input
		WCHAR strCommand[256] = {0};
		wifstream InFile( str );
		if( !InFile )
			return DXTRACE_ERR( L"wifstream::open", E_FAIL );

		for(; ; )
		{
			InFile >> strCommand;
			if( !InFile )
				break;

			if( 0 == wcscmp( strCommand, L"#" ) )
			{
				// Comment
			}
			else if( 0 == wcscmp( strCommand, L"v" ) )
			{
				// Vertex Position
				float x, y, z;
				InFile >> x >> y >> z;
				Positions.push_back( D3DXVECTOR3( x, y, z ) );
			}
			else if( 0 == wcscmp( strCommand, L"vt" ) )
			{
				// Vertex TexCoord
				float u, v;
				InFile >> u >> v;
				TexCoords.push_back( D3DXVECTOR2( u, v ) );
			}
			else if( 0 == wcscmp( strCommand, L"vn" ) )
			{
				// Vertex Normal
				float x, y, z;
				InFile >> x >> y >> z;
				Normals.push_back( D3DXVECTOR3( x, y, z ) );
			}
			else if( 0 == wcscmp( strCommand, L"f" ) )
			{
				// Face
				UINT iPosition, iTexCoord, iNormal;
				D3DXVECTOR3 *pPos, *pNorm;
				D3DXVECTOR2* pUV;
				for( UINT iFace = 0; iFace < 3; iFace++ )
				{
					pPos = NULL; pNorm = NULL; pUV = NULL;
					// OBJ format uses 1-based arrays
					InFile >> iPosition;
					pPos = &(Positions[ iPosition - 1 ]);
					if( '/' == InFile.peek() )
					{
						InFile.ignore();

						if( '/' != InFile.peek() )
						{
							// Optional texture coordinate
							InFile >> iTexCoord;
							pUV = &(TexCoords[ iTexCoord - 1 ]);
						}

						if( '/' == InFile.peek() )
						{
							InFile.ignore();
							// Optional vertex normal
							InFile >> iNormal;
							pNorm = &(Normals[ iNormal - 1 ]);
						}
					}

					// If a duplicate vertex doesn't exist, add this vertex to the Vertices
					// list. Store the index in the Indices array. The Vertices and Indices
					// lists will eventually become the Vertex Buffer and Index Buffer for
					// the mesh.
					DWORD index = AddVertex( iPosition, &(CVertType(pPos,pNorm,pUV)) );
					if ( index == (DWORD)-1 )
						return E_OUTOFMEMORY;

					m_Indices.push_back( index );
				}
				m_Attributes.push_back( dwCurSubset );
			}
			else if( 0 == wcscmp( strCommand, L"mtllib" ) )
			{
				// Material library
				InFile >> strMaterialFilename;
			}
			else if( 0 == wcscmp( strCommand, L"usemtl" ) )
			{
				// Material
				WCHAR strName[MAX_PATH] = {0};
				InFile >> strName;

				bool bFound = false;
				for( int iMaterial = 0; iMaterial < m_Materials.size(); iMaterial++ )
				{
					Material* pCurMaterial = m_Materials[iMaterial];
					if( 0 == wcscmp( pCurMaterial->strName, strName ) )
					{
						bFound = true;
						dwCurSubset = iMaterial;
						break;
					}
				}

				if( !bFound )
				{
					Material* pMaterial = new Material();
					if( pMaterial == NULL )
						return E_OUTOFMEMORY;

					dwCurSubset = m_Materials.size();

					CD3DUtilityFunction::InitMaterial( pMaterial );
					wcscpy_s( pMaterial->strName, MAX_PATH - 1, strName );

					m_Materials.push_back( pMaterial );
				}
			}
			else
			{
				// Unimplemented or unrecognized command
			}

			InFile.ignore( 1000, '\n' );
		}

		// Cleanup
		InFile.close();
		DeleteCache();

		// If an associated material file was found, read that in as well.
		if( strMaterialFilename[0] ) {
			V_RETURN( LoadMaterialsFromMTL( strMaterialFilename ) );
		}
		else{
			Material* pMaterial = new Material();
			if( pMaterial == NULL )
				return E_OUTOFMEMORY;
			CD3DUtilityFunction::InitMaterial( pMaterial );
			wcscpy_s( pMaterial->strName, MAX_PATH - 1, L"default" );
			m_Materials.push_back( pMaterial );
		}

		return S_OK;
	}

	DWORD   AddVertex( UINT hash, CVertType* pVertex )
	{
		// If this vertex doesn't already exist in the Vertices list, create a new entry.
		// Add the index of the vertex to the Indices list.
		bool bFoundInList = false;
		DWORD index = 0;

		// Since it's very slow to check every element in the vertex list, a hashtable stores
		// vertex indices according to the vertex position's index as reported by the OBJ file
		if( ( UINT )m_VertexCache.size() > hash )
		{
			CacheEntry* pEntry = m_VertexCache[hash];

			while( pEntry != NULL )
			{
				CVertType* pCacheVertex = m_Vertices.data() + pEntry->index;

				// If this vertex is identical to the vertex already in the list, simply
				// point the index buffer to the existing vertex
				if( 0 == memcmp( pVertex, pCacheVertex, sizeof( CVertType ) ) )
				{
					bFoundInList = true;
					index = pEntry->index;
					break;
				}

				pEntry = pEntry->pNext;
			}
		}
		// Vertex was not found in the list. Create a new entry, both within the Vertices list
		// and also within the hashtable cache
		if( !bFoundInList )
		{
			// Add to the Vertices list
			index = m_Vertices.size();
			m_Vertices.push_back( *pVertex );

			// Add this to the hashtable
			CacheEntry* pNewEntry = new CacheEntry;
			if( pNewEntry == NULL )
				return (DWORD)-1;

			pNewEntry->index = index;
			pNewEntry->pNext = NULL;

			// Grow the cache if needed
			while( ( UINT )m_VertexCache.size() <= hash )
			{
				m_VertexCache.push_back( NULL );
			}

			// Add to the end of the linked list
			CacheEntry* pCurEntry = m_VertexCache[hash];
			if( pCurEntry == NULL )
			{
				// This is the head element
				m_VertexCache[hash] = pNewEntry;
			}
			else
			{
				// Find the tail
				while( pCurEntry->pNext != NULL )
				{
					pCurEntry = pCurEntry->pNext;
				}

				pCurEntry->pNext = pNewEntry;
			}
		}

		return index;
	}
	//--------------------------------------------------------------------------------------
	void DeleteCache()
	{
		// Iterate through all the elements in the cache and subsequent linked lists
		for( int i = 0; i < m_VertexCache.size(); i++ )
		{
			CacheEntry* pEntry = m_VertexCache[i];
			while( pEntry != NULL )
			{
				CacheEntry* pNext = pEntry->pNext;
				SAFE_DELETE( pEntry );
				pEntry = pNext;
			}
		}

		m_VertexCache.clear();
	}


	//--------------------------------------------------------------------------------------
	HRESULT LoadMaterialsFromMTL( const WCHAR* strFileName )
	{
		HRESULT hr;

		// Set the current directory based on where the mesh was found
		WCHAR wstrOldDir[MAX_PATH] = {0};
		GetCurrentDirectory( MAX_PATH, wstrOldDir );
		SetCurrentDirectory( m_strMediaDir );

		// Find the file
		WCHAR strPath[MAX_PATH];
		char cstrPath[MAX_PATH];
		V_RETURN( DXUTFindDXSDKMediaFileCch( strPath, MAX_PATH, strFileName ) );
		WideCharToMultiByte( CP_ACP, 0, strPath, -1, cstrPath, MAX_PATH, NULL, NULL );

		// File input
		WCHAR strCommand[256] = {0};
		wifstream InFile( cstrPath );
		if( !InFile )
			return DXTRACE_ERR( L"wifstream::open", E_FAIL );

		// Restore the original current directory
		SetCurrentDirectory( wstrOldDir );

		Material* pMaterial = NULL;

		for(; ; )
		{
			InFile >> strCommand;
			if( !InFile )
				break;

			if( 0 == wcscmp( strCommand, L"newmtl" ) )
			{
				// Switching active materials
				WCHAR strName[MAX_PATH] = {0};
				InFile >> strName;

				pMaterial = NULL;
				for( int i = 0; i < m_Materials.size(); i++ )
				{
					Material* pCurMaterial = m_Materials[i];
					if( 0 == wcscmp( pCurMaterial->strName, strName ) )
					{
						pMaterial = pCurMaterial;
						break;
					}
				}
			}

			// The rest of the commands rely on an active material
			if( pMaterial == NULL )
				continue;

			if( 0 == wcscmp( strCommand, L"#" ) )
			{
				// Comment
			}
			else if( 0 == wcscmp( strCommand, L"Ka" ) )
			{
				// Ambient color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vAmbient = D3DXVECTOR3( r, g, b );
			}
			else if( 0 == wcscmp( strCommand, L"Kd" ) )
			{
				// Diffuse color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vDiffuse = D3DXVECTOR3( r, g, b );
			}
			else if( 0 == wcscmp( strCommand, L"Ks" ) )
			{
				// Specular color
				float r, g, b;
				InFile >> r >> g >> b;
				pMaterial->vSpecular = D3DXVECTOR3( r, g, b );
			}
			else if( 0 == wcscmp( strCommand, L"d" ) ||
				0 == wcscmp( strCommand, L"Tr" ) )
			{
				// Alpha
				InFile >> pMaterial->fAlpha;
			}
			else if( 0 == wcscmp( strCommand, L"Ns" ) )
			{
				// Shininess
				int nShininess;
				InFile >> nShininess;
				pMaterial->fShininess = nShininess;
			}
			else if( 0 == wcscmp( strCommand, L"illum" ) )
			{
				// Specular on/off
				int illumination;
				InFile >> illumination;
				pMaterial->bSpecular = ( illumination == 2 );
			}
			else if( 0 == wcscmp( strCommand, L"map_Kd" ) )
			{
				// Texture
				InFile >> pMaterial->strTexture;
			}

			else
			{
				// Unimplemented or unrecognized command
			}

			InFile.ignore( 1000, L'\n' );
		}

		InFile.close();

		return S_OK;
	}

public:
	//--------------------------------------------------------------------------------------
	std::vector <CacheEntry*> m_VertexCache;   // Hashtable cache for locating duplicate vertices
	std::vector <CVertType> m_Vertices;      // Filled and copied to the vertex buffer
	std::vector <VertexNormUVTang> m_Vertices_Local;
	std::vector <DWORD> m_Indices;       // Filled and copied to the index buffer
	std::vector <DWORD> m_Attributes;    // Filled and copied to the attribute buffer
	std::vector <Material*> m_Materials;     // Holds material properties per subset
	std::vector <MESH_ATTRIBUTE_RANGE> m_Subsets; //Subset mesh for rendering

	WCHAR   m_strMediaDir[ MAX_PATH ];               // Directory where the mesh was found	
};
#endif // _DEBUG


